#pragma once
#include "strv.h"

using namespace std;

extern bool g_bExceptionMade;
extern class CScript * g_pScript;
extern class CProject * g_pProject;
extern char g_szFileBuffer[2048];
extern char g_szOutputBuffer[512];
extern char g_szTempString[4096];
extern char g_szErrorString[2048];
extern string g_sTempString;
extern class CParser Parser;
extern class CConsole Console;
extern int g_nErrorStackCount;
extern DWORD g_dwVersion;
//extern bool g_bExit;

#ifdef _DEBUG
//#define DEBUGIT
#else
//#define DEBUGIT
#endif

#ifdef DEBUGIT
extern char DEBUG_STRING[4096];

#define DEBUG_TRACE printf
#define _STRING(a) #a
#define STRING(s) _STRING(s)
#else
#define DEBUG_TRACE __noop
#endif

#define TODO(s) __pragma(message("TODO: "s" ("__FUNCSIG__")"))\


typedef void*	call;

// Use to throw exceptions esp. from code errors with format
#define THROW(a,...) do{sprintf(g_szErrorString, a, __VA_ARGS__);throw(g_szErrorString);}while(0)

#ifdef _DEBUG
#define ASSERT _ASSERT
#else
#define ASSERT __noop
#endif

// This should have its own wrapper function
#define GetWorkingDir _getcwd

void			ProcessCommand(char *cmd);

// Wrapper functions for STD and API functions - for ease of future cross-compatibility
extern void				*	AlignAddress(size_t pAddress, size_t alignment=4);
extern __int64				JoinInt32(int high, int low);
extern __int64				GetTime();
extern __int64				GetFileModifiedTime(FILE *hFile);
extern __int64				GetFileModifiedTime(const char *szFilePath);
extern __int64				FiletimeToInt64(const FILETIME &ft);
extern bool					DoesDirExist(const char *szDirPath);
extern bool					DoesFileExist(const char *szFilePath);
extern bool					IsPathRelative(const char * szPath);
extern void					GetFileDirectory(const char *szFilePath, char *szDirectory, size_t nSize=0);
extern bool					FileCopy(const char *szSrcPath, const char * szDestPath, bool bOverwrite=true);
extern FILE				*	OpenExecutable(const char * szCmd, const char * szMode="r");
extern FILE				*	OpenFile(const char *szFilePath, const char *szOpenMode);
extern int					CloseExecutable(FILE *hFile);
extern void					CloseFile(FILE *hFile);
extern size_t				FileGetSize(FILE *hFile);
extern int					FileGetCursorPos(FILE *hFile);
extern bool					IsEndOfFile(FILE *hFile);
extern char				*	GetConfigLine(FILE *hFile);
extern int					SeekFile(FILE *hFile, int offset, int origin);
extern bool					WriteStringToFile(FILE *hFile, const char *str);
extern size_t				WriteBytesToFile(FILE *hFile, const void *buff, size_t len);
extern bool					ReadStringFromFile(FILE *hFile, char *str, size_t len);
extern size_t				ReadBytesFromFile(FILE *hFile, void *buff, size_t len);

template<typename T> inline T ReadFromFile(FILE *hFile);
template<typename T> inline size_t WriteToFile(FILE *hFile, T buff);
template<typename T> inline const char * tostr(const T& v);
template<typename T> inline string tostring(const T& v);
template<typename T> inline const char * tohexstr(const T v, char *out=0, size_t bytes=4);

extern inline int		NumMatchingBits(DWORD a, DWORD b);

int			GetIntNumBytes(int nVal);
int			GetDWordNumBytes(DWORD dwVal);

extern inline int			IsAlphabetic(char c);
extern inline int			IsAlphanumeric(char c);
extern inline int			IsDecimal(char c);
extern inline int			IsHex(char c);
extern inline int			IsSpace(char c);
extern inline int			IsPrint(char c);
extern inline int			IsUpper(char c);
extern inline int			IsLower(char c);
extern inline bool			IsIdentifierChar(char c);

extern inline int			ToUpper(int c);
extern inline int			ToLower(int c);

bool		IsStringHex(const char *szStr, int nLimit=0);
bool		IsStringInt(const char *szStr);
bool		IsStringFloat(const char *szStr);

char		*	StringToUpper(char *szStr, int nLimit=0);
char		*	StringToLower(char *szStr, int nLimit=0);
int			StringToInt(const char *szStr);
int			StringToInt(const char *szStr, char *&pStr);
int			StringToHex(const char *szStr);
int			StringToHex(const char *szStr, char *&pStr);
double		StringToFloat(const char *szStr);
double		StringToFloat(const char *szStr, char *&pStr);

void			ReverseString(char *begin, char *end);

int				strchrcount(const char *szStr, int c);
template<typename T> T strrnthchr(T szStr, int c, int n);
char			*	szncpy(char *out, const char *src, size_t size);
char			*	trim(char *szStr, const char * szChars = " \t\r\n");
char			*	ltrim(char *szStr, const char * szChars = " \t\r\n");
const char	*	ltrim(const char *szStr, const char * szChars = " \t\r\n");
char			*	rtrim(char *szStr, const char * szChars = " \t\r\n");

// std::stream stuff
void			Alert(const char *szStr);
void			Print(const char *szStr);
#define		Printf(a, ...) fprintf(stdout, ">> "a"\n", __VA_ARGS__)
void			Output(const char *szStr);
void			FlushOutput();
void			Error(const char *szStr);
void			Trace(const char *szStr);
void			Exception(const char *szStr);

enum eConversion
{
	CASE_CONVERT_NONE,
	CASE_CONVERT_UPPER,
	CASE_CONVERT_LOWER,
};
enum eTypeMatchRestriction
{
	MATCH_RESTRICTION_NONE,
	MATCH_RESTRICTION_BASIC,
	MATCH_RESTRICTION_STRICT,
};

enum eCurrentDir
{
	CURRENT_DIR_LOCAL,
	CURRENT_DIR_INSTALL,
	CURRENT_DIR_DEFINITIONS,
	CURRENT_DIR_PROJECT,
	CURRENT_DIR_LIBRARY,
	CURRENT_DIR_APP_LIBRARY,
	CURRENT_DIR_SCRAMBLDEF,
};

template<class T>
class CRange
{
	T	m_Min;
	T	m_Max;
	
public:
	CRange(T min, T max) : m_Min(min), m_Max(max)
	{}

	inline bool		InRange(T v)						{return v >= m_Min && v <= m_Max;}

	inline bool		operator==(T &v) const			{return InRange(v);}
	inline bool		operator!=(T &v) const			{return !(*this == v);}
	inline bool		operator>(T &v) const			{return v > m_Max;}
	inline bool		operator<(T &v) const			{return v < m_Min;}
	inline bool		operator>=(T &v) const			{return v >= m_Min;}
	inline bool		operator<=(T &v) const			{return v <= m_Max;}
};

extern class CSCRambl SCRambl;

class CMyName
{
	// stolen from CIdentifier
	char			*	m_szName;
	size_t				m_nLength;
	DWORD				m_dwHash;

protected:
	CMyName(const char *szName)
	{
		if(szName && *szName)
		{
			m_nLength = strlen(szName);
			m_szName = new char[m_nLength+1];
			szncpy(m_szName, szName, m_nLength+1);
			m_dwHash = GenerateHash(m_szName);
		}
		else
		{
			m_szName = nullptr;
			m_dwHash = 0xFFFFFFFF;
		}
	}
	~CMyName()
	{
		if(m_szName) delete[] m_szName;
	}

private:
	inline bool IsEqual(const CMyName &v) const						{return GetHash() == v.GetHash() && GetNameLength() == v.GetNameLength();}
	
public:
	inline size_t			GetNameLength()	 const					{return m_nLength;}
	inline const char	*	GetName() const							{return m_szName;}
	inline void				GetName(char *szBuf) const				{szncpy(szBuf, m_szName, m_nLength+1);}
	inline DWORD			GetHash() const								{return m_dwHash;}

	// slow success, slow fail (100% accurate)
	inline bool IsNamed(const char *szName) const					{return strncmp(szName, m_szName, m_nLength) == 0;}
	// slow success, fast fail (100% accurate)
	inline bool IsNamed(const char *szName, DWORD dwHash) const		{return dwHash == GetHash() && IsNamed(szName);}
	// fast success, fast fail (~98% accurate)
	//inline bool IsNamed(DWORD dwHash) const							{return dwHash == GetHash();}
	// fast success, fast fail (~99.9% accurate)
	inline bool IsNamed(const CMyName &v) const						{return GetHash() == v.GetHash() && GetNameLength() == v.GetNameLength();}
};

class CLanguageEntry : public CMyName
{
	std::string			m_sText;

public:
	CLanguageEntry(const char * szName, const char * str) : CMyName(szName),
		m_sText(str)
	{
	}

	inline void				Set(const char * str)				{m_sText = str;}
	inline const char	*	Get() const							{return m_sText.c_str();}
	inline size_t			Length() const						{return m_sText.length();}
};
class CLanguageTable : public CMyName
{
	friend class CLanguage;

	class CLanguage		*	m_pLanguage;
	int						m_nNumEntries;
	CList<CLanguageEntry>	m_Entries;

	CLanguageTable(const char * szName, class CLanguage * pLang) : CMyName(szName),
		m_pLanguage(pLang),
		m_nNumEntries(0)
	{
	}

public:
	inline const CLanguageEntry	*	Get(const char * szEntry, DWORD dwHash) const
	{
		return m_Entries.Get(dwHash);
	}
	inline const CLanguageEntry	*	Get(const char * szEntry) const
	{
		return Get(szEntry, GenerateHash(szEntry));
	}
	inline void Add(CLanguageEntry * pEntry)
	{
		if(pEntry)
		{
			m_Entries.Add(pEntry->GetHash(), pEntry);
			++m_nNumEntries;
		}
	}
};
class CLanguage
{
	CList<CLanguageTable>		m_Tables;

public:
	CLanguageTable		*		AddTable(const char * szName)
	{
		if(auto pLangTable = new CLanguageTable(szName, this))
		{
			m_Tables.Add(pLangTable->GetHash(), pLangTable);
			return pLangTable;
		}
		return nullptr;
	}
	const CLanguageTable	*	GetTable(const char * szName, DWORD dwHash) const
	{
		return m_Tables.Get(dwHash);
	}
	inline const CLanguageTable	*	GetTable(const char * szName) const
	{
		return GetTable(szName, GenerateHash(szName));
	}
};

class CSCRambl
{
public:
	size_t					m_nMemorySize;
	size_t					m_nMemoryUsed;
	bool						m_bConsoleMode;
	eCurrentDir				m_eSavedDir;
	char						m_szDir[MAX_PATH];
	char						m_szProjectDir[MAX_PATH];
	char						m_szLibraryDir[MAX_PATH];
	char						m_szLangTemp[2048];

	CLanguage				Language;

private:
	bool RealChangeCurrentDirectory(eCurrentDir eDir)
	{
		switch(eDir)
		{
		case CURRENT_DIR_LOCAL:
			return chdir(m_szDir) != -1;
		case CURRENT_DIR_INSTALL:
			return IsGameInfoLoaded() ? chdir(Config.Installs.GetApp()->m_szInstallPath) != -1 : false;
		case CURRENT_DIR_DEFINITIONS:
			return IsGameInfoLoaded() ? chdir(Config.Installs.GetApp()->m_szDefinitionPath) != -1 : false;
		case CURRENT_DIR_PROJECT:
			return GetProjectDirectory() ? chdir(GetProjectDirectory()) != -1 : false;
		case CURRENT_DIR_LIBRARY:
			return *m_szLibraryDir ? chdir(m_szLibraryDir) != -1 : false;
		case CURRENT_DIR_APP_LIBRARY:
			return IsGameInfoLoaded() ? chdir(Config.Installs.GetApp()->m_szLibraryPath) != -1 : false;
		case CURRENT_DIR_SCRAMBLDEF:
			return chdir(Config.Console.m_szDefinitionPath) != -1;
		}
		return false;
	}

public:
	class CConfig
	{
	public:
		char				m_szLanguageFilePath[MAX_PATH];

		void ReadConfig(const char * szPath)
		{
			std::string sIniPath = std::string(SCRambl.m_szDir) + "\\" + std::string(szPath);
			const char * szIniPath = sIniPath.c_str();
			GetPrivateProfileString("MAIN", "LanguageFile", "lang\\en.ini", m_szLanguageFilePath, sizeof(m_szLanguageFilePath), szIniPath);
		}

		class CConsole
		{
		public:
			char			m_cID;
			char			m_szDefinitionPath[MAX_PATH];

			void ReadConfig(const char * szPath)
			{
				//std::string sIniPath = std::string(SCRambl.m_szDir) + "\\" + std::string(szPath);
				//const char * szIniPath = sIniPath.c_str();
				//GetPrivateProfileString("CONSOLE", "DefinitionPath", "config\\scrambl", m_szDefinitionPath, sizeof(m_szDefinitionPath), szIniPath);
			}
		} Console;

		class CInstalls
		{
			int m_nNumInstalls;
			int m_nCurrentInstall;
			
			class CSCRApp
			{
			public:
				char m_cID;			// 'S'an An, 'M'iami, 'L'iberty, 'V'igilante Justice, etc.
				char m_szDefinitionPath[MAX_PATH];
				char m_szLibraryPath[MAX_PATH];
				char m_szInstallPath[MAX_PATH];
				char m_szDatFilePath[MAX_PATH];
				char m_szIdeFilePath[MAX_PATH];
				char m_szPedFilePath[MAX_PATH];
				char m_szVehFilePath[MAX_PATH];
				bool m_bHavePedFilePath;
				bool m_bHaveVehFilePath;

				CSCRApp(char cID)
				{
					m_cID = cID;
					m_bHavePedFilePath = false;
					m_bHaveVehFilePath = false;
				}
			} * m_pApplications[50];

		public:
			CInstalls()
			{
				m_nNumInstalls = 0;
				m_nCurrentInstall = 0;
				for(int i=0; i<sizeof(m_pApplications); i++)
					m_pApplications[i] = nullptr;
			}
			void AddApp(CSCRApp * pApp)
			{
				for(int i=0; i<m_nNumInstalls; i++)
				{
					if(pApp->m_cID == m_pApplications[i]->m_cID)
						THROW("Duplicate game install ID '%c'", pApp->m_cID);
				}
				m_pApplications[m_nNumInstalls++] = pApp;
			}
			CSCRApp * GetApp()
			{
				return m_pApplications[m_nCurrentInstall];
			}
			void ReadConfig(const char * szPath)
			{
				CSCRApp * pApp;
				const char * pStr = g_szTempString;
				char szTempPath[MAX_PATH];
				std::string sIniPath = std::string(SCRambl.m_szDir) + "\\" + std::string(szPath);
				const char * szIniPath = sIniPath.c_str();
				GetPrivateProfileString("APPS", NULL, NULL, g_szTempString, sizeof(g_szTempString), szIniPath);

				while(*pStr)
				{
					GetPrivateProfileString(pStr, "ID", NULL, szTempPath, 2, szIniPath);
					if(*szTempPath)
					{
						pApp = new CSCRApp(*szTempPath);
						GetPrivateProfileString(pStr, "InstallPath", NULL, pApp->m_szInstallPath, sizeof(pApp->m_szInstallPath), szIniPath);
						GetPrivateProfileString(pStr, "DefinitionPath", NULL, pApp->m_szDefinitionPath, sizeof(pApp->m_szDefinitionPath), szIniPath);
						GetPrivateProfileString(pStr, "LibraryPath", NULL, pApp->m_szLibraryPath, sizeof(pApp->m_szLibraryPath), szIniPath);

						if(*pApp->m_szInstallPath && *pApp->m_szDefinitionPath)
						{
							// Get .dat path
							GetPrivateProfileString(pStr, "DatFile", "data\\gta.dat", szTempPath, sizeof(szTempPath), szIniPath);
							strcpy(pApp->m_szDatFilePath, pApp->m_szInstallPath);
							strcat(pApp->m_szDatFilePath, "\\");
							strcat(pApp->m_szDatFilePath, szTempPath);
				
							// Get .ide path
							GetPrivateProfileString(pStr, "IdeFile", "data\\default.ide", szTempPath, sizeof(szTempPath), szIniPath);
							strcpy(pApp->m_szIdeFilePath, pApp->m_szInstallPath);
							strcat(pApp->m_szIdeFilePath, "\\");
							strcat(pApp->m_szIdeFilePath, szTempPath);
				
							// Get peds.ide path
							GetPrivateProfileString(pStr, "PedsFile", "", szTempPath, sizeof(szTempPath), szIniPath);
							if(*szTempPath)
							{
								pApp->m_bHavePedFilePath = true;
								strcpy(pApp->m_szPedFilePath, pApp->m_szInstallPath);
								strcat(pApp->m_szPedFilePath, "\\");
								strcat(pApp->m_szPedFilePath, szTempPath);
							}

							// Get vehicles.ide path
							GetPrivateProfileString(pStr, "VehiclesFile", "", szTempPath, sizeof(szTempPath), szIniPath);
							if(*szTempPath)
							{
								pApp->m_bHaveVehFilePath = true;
								strcpy(pApp->m_szVehFilePath, pApp->m_szInstallPath);
								strcat(pApp->m_szVehFilePath, "\\");
								strcat(pApp->m_szVehFilePath, szTempPath);
							}

							AddApp(pApp);
						}
						else delete pApp;
					}
					
					pStr = strchr(pStr, '\0') + 1;
				}
			}
		} Installs;
		class CParser
		{
			eConversion				m_eCaseConversion;
			eTypeMatchRestriction	m_eTypeMatchRestriction;
			bool					m_bUseOneBasedIndex;
			bool					m_bCreateListFile;

			int						m_nCaseConversionStackIdx;
			eConversion				m_eCaseConversionStack[64];
			int						m_nTypeMatchRestrictionStackIdx;
			eTypeMatchRestriction	m_eTypeMatchRestrictionStack[64];
			int						m_nUseOneBasedIndexStackIdx;
			bool					m_bUseOneBasedIndexStack[64];
			int						m_nCreateListFileStackIdx;
			bool					m_bCreateListFileStack[64];
			
		public:
			CParser()	:	m_nCaseConversionStackIdx(0),
							m_nTypeMatchRestrictionStackIdx(0),
							m_nUseOneBasedIndexStackIdx(0),
							m_nCreateListFileStackIdx(0)
			{
			}

			inline bool						UseOneBasedIndex()					{return m_bUseOneBasedIndex;}
			inline bool						CreateListFile()					{return m_bCreateListFile;}
			inline eConversion				GetCaseConversion()					{return m_eCaseConversion;}
			inline eTypeMatchRestriction	GetTypeMatchRestriction()			{return m_eTypeMatchRestriction;}

			void ResetConfig()
			{
				if(m_nCaseConversionStackIdx)
					m_eCaseConversion = m_eCaseConversionStack[m_nCaseConversionStackIdx = 0];
				if(m_nTypeMatchRestrictionStackIdx)
					m_eTypeMatchRestriction = m_eTypeMatchRestrictionStack[m_nTypeMatchRestrictionStackIdx = 0];
				if(m_nUseOneBasedIndexStackIdx)
					m_bUseOneBasedIndex = m_bUseOneBasedIndexStack[m_nUseOneBasedIndexStackIdx = 0];
				if(m_nCreateListFileStackIdx)
					m_bCreateListFile = m_bCreateListFileStack[m_nCreateListFileStackIdx = 0];
			}

			bool PushConfig(const char * szSetting, const char * szValue)
			{
				if(stricmp(szSetting, "CaseConversion"))
				{
					if(stricmp(szSetting, "TypeMatchRestriction"))
					{
						if(stricmp(szSetting, "OneBasedIndex"))
						{
							if(stricmp(szSetting, "CreateListFile"))
							{
								return false;
							}
							else
							{
								if(IsStringInt(szValue))
								{
									m_bCreateListFileStack[m_nCreateListFileStackIdx++] = m_bCreateListFile;
									m_bCreateListFile = StringToInt(szValue) != 0;
								}
								else return false;
							}
						}
						else
						{
							if(IsStringInt(szValue))
							{
								m_bUseOneBasedIndexStack[m_nUseOneBasedIndexStackIdx++] = m_bUseOneBasedIndex;
								m_bUseOneBasedIndex = StringToInt(szValue) != 0;
							}
							else return false;
						}
					}
					else
					{
						m_bUseOneBasedIndexStack[m_nUseOneBasedIndexStackIdx++] = m_bUseOneBasedIndex;
						if(stricmp(szValue, "STRICT"))
						{
							if(stricmp(szValue, "BASIC"))
							{
								if(stricmp(szValue, "NONE"))
								{
									m_bUseOneBasedIndex = m_bUseOneBasedIndexStack[--m_nUseOneBasedIndexStackIdx];
									return false;
								}
								else m_eTypeMatchRestriction = MATCH_RESTRICTION_NONE;
							}
							else m_eTypeMatchRestriction = MATCH_RESTRICTION_BASIC;
						}
						else m_eTypeMatchRestriction = MATCH_RESTRICTION_STRICT;
					}
				}
				else
				{
					m_eCaseConversionStack[m_nCaseConversionStackIdx++] = m_eCaseConversion;
					if(stricmp(szValue, "UPPER"))
					{
						if(stricmp(szValue, "LOWER"))
						{
							if(stricmp(szValue, "NONE"))
							{
								m_eCaseConversion = m_eCaseConversionStack[--m_nCaseConversionStackIdx];
								return false;
							}
							else m_eCaseConversion = CASE_CONVERT_NONE;
						}
						else m_eCaseConversion = CASE_CONVERT_LOWER;
					}
					else m_eCaseConversion = CASE_CONVERT_UPPER;
				}
				return true;
			}

			bool PopConfig(const char * szSetting)
			{
				if(stricmp(szSetting, "CaseConversion"))
				{
					if(stricmp(szSetting, "TypeMatchRestriction"))
					{
						if(stricmp(szSetting, "UseOneBasedIndex"))
						{
							if(stricmp(szSetting, "CreateListFile"))
							{
								return false;
							}
							else m_bCreateListFile = m_bCreateListFileStack[--m_nCreateListFileStackIdx];
						}
						else m_bUseOneBasedIndex = m_bUseOneBasedIndexStack[--m_nUseOneBasedIndexStackIdx];
					}
					else m_bUseOneBasedIndex = m_bUseOneBasedIndexStack[--m_nUseOneBasedIndexStackIdx];
				}
				else m_eCaseConversion = m_eCaseConversionStack[--m_nCaseConversionStackIdx];
				return true;
			}

			void ReadConfig(const char * szPath)
			{
				char szTempString[64];
				std::string sIniPath = std::string(SCRambl.m_szDir) + "\\" + std::string(szPath);
				const char * szIniPath = sIniPath.c_str();
				GetPrivateProfileString("PREPARSER", "CaseConversion", "UPPER", szTempString, sizeof(szTempString), szIniPath);
				strtok(szTempString, " \t;");

				if(!stricmp("LOWER", szTempString))
					m_eCaseConversion = CASE_CONVERT_LOWER;
				else if(!stricmp("UPPER", szTempString))
					m_eCaseConversion = CASE_CONVERT_UPPER;
				else
					m_eCaseConversion = CASE_CONVERT_NONE;

				GetPrivateProfileString("PREPARSER", "TypeMatchRestriction", "NONE", szTempString, sizeof(szTempString), szIniPath);
				strtok(szTempString, " \t;");

				if(!stricmp("BASIC", szTempString))
					m_eTypeMatchRestriction = MATCH_RESTRICTION_BASIC;
				else if(!stricmp("STRICT", szTempString))
					m_eTypeMatchRestriction = MATCH_RESTRICTION_STRICT;
				else
					m_eTypeMatchRestriction = MATCH_RESTRICTION_NONE;

				m_bUseOneBasedIndex = GetPrivateProfileInt("PREPARSER", "OneBasedIndex", 1, szIniPath) ? true : false;
				m_bCreateListFile = GetPrivateProfileInt("PREPARSER", "CreateListFile", 1, szIniPath) ? true : false;
			}
		} Parser;
		class COptimisation
		{
			enum eOptimisation
			{
				OPTIMISE_MIN,
				OPTIMISE_MED,
				OPTIMISE_MAX,
				OPTIMISE_CUSTOM,
			} m_eOptimiseLevel;
			bool m_bSkipUselessIFs;
			bool m_bSkipSelfAssignment;
			bool m_bSkipUselessJumps;
			int m_nAsmMultipassLevel;

		public:
			inline bool				SkipUselessIFs()					{return m_bSkipUselessIFs;}
			inline bool				SkipSelfAssignment()				{return m_bSkipSelfAssignment;}
			inline int				GetAsmMultipassLevel()				{return m_nAsmMultipassLevel;}
			void						ReadConfig(const char * szPath)
			{
				char szTempString[64];
				std::string sIniPath = std::string(SCRambl.m_szDir) + "\\" + std::string(szPath);
				const char * szIniPath = sIniPath.c_str();
				GetPrivateProfileString("OPTIMISATION", "OptimisationLevel", "CUSTOM", szTempString, sizeof(szTempString), szIniPath);

				if(stricmp(szTempString, "MAX"))
				{
					if(stricmp(szTempString, "MED"))
					{
						if(stricmp(szTempString, "MIN"))
						{
							ChangeOptimisationLevel(OPTIMISE_CUSTOM);
							m_nAsmMultipassLevel = GetPrivateProfileInt("OPTIMISATION", "AssemblyPassLevel", 0, szIniPath);
							m_bSkipUselessIFs = GetPrivateProfileInt("OPTIMISATION", "SkipUselessIFs", 0, szIniPath) ? true : false;
							m_bSkipSelfAssignment = GetPrivateProfileInt("OPTIMISATION", "SkipSelfAssignment", 0, szIniPath) ? true : false;
							m_bSkipUselessJumps = GetPrivateProfileInt("OPTIMISATION", "SkipUselessJumps", 0, szIniPath) ? true : false;
						}
						else ChangeOptimisationLevel(OPTIMISE_MIN);
					}
					else ChangeOptimisationLevel(OPTIMISE_MED);
				}
				else ChangeOptimisationLevel(OPTIMISE_MAX);
			}
			void					ChangeOptimisationLevel(eOptimisation eLvl)
			{
				switch(eLvl)
				{
				default:
					// Use MIN defaults for custom mode
					eLvl = OPTIMISE_CUSTOM;
				case OPTIMISE_MIN:
					m_eOptimiseLevel = eLvl;
					m_nAsmMultipassLevel = 0;
					m_bSkipUselessIFs = false;
					m_bSkipSelfAssignment = true;
					m_bSkipUselessJumps = false;
					break;
				case OPTIMISE_MED:
					m_eOptimiseLevel = OPTIMISE_MED;
					m_nAsmMultipassLevel = 1;
					m_bSkipUselessIFs = true;
					m_bSkipSelfAssignment = true;
					m_bSkipUselessJumps = true;
					break;
				case OPTIMISE_MAX:
					m_eOptimiseLevel = OPTIMISE_MAX;
					m_nAsmMultipassLevel = 2;
					m_bSkipUselessIFs = true;
					m_bSkipSelfAssignment = true;
					m_bSkipUselessJumps = true;
					break;
				};
			}
		} Optimisation;
		class CPerformance
		{
			size_t m_nInputBufferSize;
			size_t m_nMaxMemory;

		public:
			inline int GetInputBufferSize()								{return m_nInputBufferSize;}
			inline int GetMaxMemory()									{return m_nMaxMemory;}

			void ReadConfig(const char * szPath)
			{
				std::string sIniPath = std::string(SCRambl.m_szDir) + "\\" + std::string(szPath);
				const char * szIniPath = sIniPath.c_str();
				m_nInputBufferSize = GetPrivateProfileInt("PERFORMANCE", "InputBufferSize", 1024, szIniPath);
				m_nMaxMemory = GetPrivateProfileInt("PERFORMANCE", "MaxMemory", 204800, szIniPath);
			}
		} Performance;
	} Config;

	CSCRambl()
	{
		Output(
			"*****************************************\n"
			"*	SCRambl (SCR Assembler)		*\n"
			"*****************************************\n"
			"*	@ Created by Deji (2012)	*\n"
			"*****************************************\n"
		);

		*m_szProjectDir = '\0';
		m_bConsoleMode = false;

		// Identify the version
		g_dwVersion = (VERSION_MAJOR << 24) | (VERSION_MINOR << 16) | (VERSION_REVISION << 8) | VERSION_BUILD;

		// Save the current directory
		GetWorkingDir(m_szDir, sizeof(m_szDir));

		// Save library directory
		szncpy(m_szLibraryDir, m_szDir, sizeof(m_szLibraryDir));
		strcat(m_szLibraryDir, "\\lib");

		// Read MAIN config
		Config.ReadConfig("config.ini");

		// Load language file
		LoadLanguage(Config.m_szLanguageFilePath);

		try
		{
			// Load config.ini
			LoadConfig("config.ini");

			m_nMemorySize = Config.Performance.GetMaxMemory() / 16;

			if(!IsGameInfoLoaded())
				Print(GetText("GENERAL", "M100"));

			// Add console stuff
			InitConsole();

			// Load SCR definitions
			if(ChangeCurrentDirectory(CURRENT_DIR_DEFINITIONS))
			{
				LoadDefinitions();
				ResetCurrentDirectory();
			}
			
			// Load application data
			if(IsGameInfoLoaded())
				LoadGameFiles();
		}
		catch(const char *e)
		{
			Error(e);
			Exception(GetText("GENERAL", "M101"));
		}
	}

	inline void InConsoleMode(bool b)
	{
		m_bConsoleMode = b;
	}
	inline bool InConsoleMode()
	{
		return m_bConsoleMode;
	}
	inline bool IsGameInfoLoaded()
	{
		return Config.Installs.GetApp() != nullptr;
	}

	inline void SetProjectDirectory(const char * szDir)
	{
		szncpy(m_szProjectDir, szDir, sizeof(m_szProjectDir));
	}
	inline const char * GetProjectDirectory()
	{
		return *m_szProjectDir ? m_szProjectDir : nullptr;
	}
	inline eCurrentDir GetActiveDirectory()						{return m_eSavedDir;}
	inline bool ChangeCurrentDirectory(eCurrentDir eDir)
	{
		if(RealChangeCurrentDirectory(eDir))
		{
			m_eSavedDir = eDir;
			return true;
		}
		return false;
	}
	inline bool ResetCurrentDirectory()
	{
		return ChangeCurrentDirectory(CURRENT_DIR_LOCAL);
	}

	void LoadConfig(const char * szPath)
	{
		// Read config through modules.. fancy..
		Config.Parser.ReadConfig(szPath);
		Config.Performance.ReadConfig(szPath);
		Config.Optimisation.ReadConfig(szPath);
		Config.Installs.ReadConfig(szPath);
	}
	void LoadLanguage(const char * szPath)
	{
		if(FILE * hFile = OpenFile(szPath, "rt"))
		{
			CLanguageTable * pLangTable = nullptr;
			char szTableName[32];
			while(char * szLine = GetConfigLine(hFile))
			{
				if(*szLine=='[')
				{
					if(szLine = strtok(++szLine, "]"))
					{
						szncpy(szTableName, szLine, sizeof(szTableName));
						pLangTable = Language.AddTable(szTableName);
					}
				}
				else if(pLangTable)
				{
					if(char * szEntry = strtok(szLine, "="))
					{
						if(szLine = strtok(nullptr, "\n"))
						{
							if(szLine = trim(szLine))
								pLangTable->Add(new CLanguageEntry(szEntry, szLine));
						}
					}
				}
			}
			CloseFile(hFile);
		}
		else THROW("Failed to open file '%s'", szPath);
	}
	static const char * GetText(const char * szTable, const char * szEntry)
	{
		if(auto pTable = SCRambl.Language.GetTable(szTable))
		{
			if(auto pEntry = pTable->Get(szEntry))
				return pEntry->Get();
		}
		return "";
	}

	// Builds a translatable text string with orderable wildcards and formatting
	static char * BuildText(const char * szText, char * szOut, size_t nSize, const char *szFormat = nullptr, ...)
	{
		static char fmt[30][8];
		static int ararg[8];
		va_list args;
		int n = 0;
		if(!szOut)
		{
			szOut = SCRambl.m_szLangTemp;
			nSize = sizeof(SCRambl.m_szLangTemp);
		}
		if(szFormat)
		{
			va_start(args, szFormat);
			int i = 0;
			while(*szFormat)
			{
				if(*szFormat == '%')
				{
					ararg[n] = va_arg(args, int);
					fmt[n++][i] = '\0';
					i = 0;
				}
				else if(i < (sizeof(fmt[n-1]) - 1)) fmt[n-1][i++] = *szFormat;
				++szFormat;
			}
			//if(n) ararg[n] = va_arg(args, int);
			va_end(args);
		}
		static char buf[0x1000];
		auto pBuf = buf;
		while(*szText)
		{
			if(szFormat)
			{
				if(*szText == '%' && IsDecimal(szText[1]) && szText[-1] != '%')
				{
					char * szEnd;
					if(int nIdx = StringToInt(++szText, szEnd))
					{
						if(--nIdx <= n)
						{
							*pBuf++ = '%';
							strcpy(pBuf, fmt[nIdx]);
							while(*pBuf) ++pBuf;
							if(szEnd) szText = szEnd;
							continue;
						}
					}
					if(szEnd) szText = szEnd;
					else ++szText;
					continue;
				}
				//else if(szText[-1] != '%') continue;
			}
			*pBuf++ = *szText++;
		}
		*pBuf++ = '\0';
		sprintf(szOut, buf, ararg[0], ararg[1], ararg[2], ararg[3], ararg[4], ararg[5], ararg[6], ararg[7]);
		return szOut;
	}

	static const char * GetFullPath(const char * szPath, char * szOut=nullptr);

	static char * ReadConfigLine(FILE *hFile)
	{
#if FALSE
		char * str;
		while(str = fgets(g_szTempString, 512, hFile))
		{
			while(*str && IsSpace(*str)) str++;
			switch(*str)
			{
			case '\0':
			case ';':
			case '\n':
				continue;
			}
			return strtok(str, "\n");
		}
		return false;
#endif
		return GetConfigLine(hFile);
	}

	void InitConsole();
	static void LoadDefinitions();
	static void LoadGameFiles();
};